package cn.liangent.travlib;

import static cn.liangent.travlib.util.Utilities.*;
import HTTPClient.*;
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.logging.*;

public class Client {
	private static final Logger logger = Logger.getLogger(Client.class.getCanonicalName());
	private final HTTPConnection hc;
	private final User user;

	// cache of village list
	private boolean startedToCacheVillageList = false;
	private boolean singleVillage;
	private HashMap<Integer, Village> cachedVillageList;
	// end of cache of village list

	static {
		CookieModule.setCookiePolicyHandler(null);
	}
	
	/**
	 * @throw UnsupportedOperationException protocol not supported [only http (or https?) is ok]
	 */
	public Client(User user) throws UnsupportedOperationException {
		if (user == null) throw new NullPointerException();
		try {
			hc = new HTTPConnection(user.getServer());
		} catch (ProtocolNotSuppException ex) {
			throw new UnsupportedOperationException(ex);
		}
		hc.setContext(user);
		this.user = user;
	}
	
	/**
	 * never returns null
	 */
	public User getUser() {
		return user;
	}
	
	public Village[] getVillages() throws IOException, HttpException, AuthenticationException, BadPageException {
		String pgsrc = httpGet("/dorf3.php");
		String[] slist = findAll(pgsrc, "\\Q<a href=\"dorf1.php?newdid=\\E(\\d+)\\Q\">\\E.*?\\Q</a>\\E");
		int[] list = new int[slist.length];
		for (int i = 0; i < slist.length; i++) {
			try {
				list[i] = Integer.parseInt(slist[i]);
			} catch (Exception ex) {
				throw new BadPageException(ex);
			}
		}
		Village[] vlg = new Village[list.length];
		for (int i = 0; i < vlg.length; i++) {
			vlg[i] = new Village(this, list[i]);
		}
		return vlg;
	}

	public Village getDefaultVillage() {
		return new Village(this, 0);
	}
	
	/**
	 * @return source of *default* dorf1.php
	 */
	private String loginByPageSrc(String src, int count) throws IOException, HttpException, AuthenticationException, BadPageException {
		if (count >= 5) throw new AuthenticationException();
		NVPair[] param = new NVPair[] { // press Enter, not click on image
			new NVPair("w", ""),
			new NVPair("login",
				find(src, "\\Q<input type=\"hidden\" name=\"login\" value=\"\\E(\\d*)\\Q\">\\E")),
			new NVPair(find(src, "\\Q<input class=\"fm fm110\" type=\"text\" " +
					"name=\"\\E(\\p{XDigit}+)\\Q\" value=\"\\E.*?\\Q\" maxlength=\"15\">\\E"),
				user.getUsername()),
			new NVPair(find(src, "\\Q<input class=\"fm fm110\" type=\"password\" " +
					"name=\"\\E(\\p{XDigit}+)\\Q\" value=\"\\E.*?\\Q\" maxlength=\"20\">\\E"),
				user.getPassword()),
			findNVPair(src, "\\Q<p align=\"center\"><input type=\"hidden\" " +
				"name=\"\\E(\\p{XDigit}+)\\Q\" value=\"\\E(\\p{XDigit}*)\\Q\">"),
			new NVPair("autologin", "ja")
		};
		logger.info("ready to login by page source, params(fine: sensitive data) are shown below:");
		logger.fine("source:");
		logger.fine(src);
		logger.fine("params:");
		logger.fine(Arrays.toString(param));
		// cannot invoke httpPost directly
		// login twice? (if didn't succeed at first) or loop invoking?
		String resp;
		try {
			resp = new String(hc.Post("/dorf1.php", param).getData(), user.getCharset());
		} catch (ModuleException ex) {
			throw new HttpException(ex);
		}
		if (haveToLogin(resp)) {
			return loginByPageSrc(resp, count + 1);
		}
		if (resp.contains("dorf1.php?ok")) {
			// is there any problem?
			// mixing AuthorizationException & HttpException?
			logger.info("a dorf1.php?ok confirmation is missed, it says: (shown in Level.FINE)");
			logger.fine(resp);
			return httpGet("/dorf1.php?ok", count + 1);
		} else {
			return resp;
		}
	}
	
	/* add a NVPair[] parameter? */
	String httpGet(String path) throws IOException, HttpException, AuthenticationException, BadPageException {
		return httpGet(path, 0);
	}

	String httpPost(String path, NVPair[] form) throws IOException, HttpException, AuthenticationException, BadPageException {
		return httpPost(path, form, 0);
	}
	
	/**
	 * @throws BadPageException if it requires to login but page is bad
	 */
	private String httpGet(String path, int count) throws IOException, HttpException, AuthenticationException, BadPageException {
		if (count >= 5) throw new HttpException("retried too many times");
		String pgsrc;
		try {
			pgsrc = new String(hc.Get(path).getData(), user.getCharset());
		} catch (ModuleException ex) { // should we catch RuntimeException here?
			throw new HttpException(ex);
		}
		if (haveToLogin(pgsrc)) {
			if (path.equals("/dorf1.php") || 
				path.equals("dorf1.php") ||
				path.equals("/dorf1.php?newdid=0") ||
				path.equals("dorf1.php?newdid=0")) { // any more paths?
				return loginByPageSrc(pgsrc, count + 1);
			} else {
				loginByPageSrc(pgsrc, count + 1);
				return httpGet(path, count + 1);
			}
		} else {
			return pgsrc;
		}
	}

	private String httpPost(String path, NVPair[] form, int count) throws IOException, HttpException, AuthenticationException, BadPageException {
		if (count >= 5) throw new HttpException("retried too many times");
		String pgsrc;
		try {
			pgsrc = new String(hc.Post(path, form).getData(), user.getCharset());
		} catch (ModuleException ex) { // should we catch RuntimeException here?
			throw new HttpException(ex);
		}
		if (haveToLogin(pgsrc)) {
			loginByPageSrc(pgsrc, count + 1);
			return httpPost(path, form, count + 1);
		} else {
			return pgsrc;
		}
	}
}
